﻿using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Effects;

namespace Black.SurfaceToolkit.Effects
{
    /// <summary>
    /// SwirlEffect Effect
    /// </summary>
    public class SwirlEffect : ShaderEffect
    {
        #region Propriétées de dépendances

        public static readonly DependencyProperty InputProperty = ShaderEffect.RegisterPixelShaderSamplerProperty("Input", typeof(SwirlEffect), 0);
        public static readonly DependencyProperty CenterProperty = DependencyProperty.Register("Center", typeof(Point), typeof(SwirlEffect), new UIPropertyMetadata(new Point(0.5, 0.5), ShaderEffect.PixelShaderConstantCallback(0)));
        public static readonly DependencyProperty SwirlStrengthProperty = DependencyProperty.Register("SwirlStrength", typeof(double), typeof(SwirlEffect), new UIPropertyMetadata(0.5, ShaderEffect.PixelShaderConstantCallback(1)));
        public static readonly DependencyProperty AngleFrequencyProperty = DependencyProperty.Register("AngleFrequency", typeof(Vector), typeof(SwirlEffect), new UIPropertyMetadata(new Vector(1, 1), ShaderEffect.PixelShaderConstantCallback(2)));

        #endregion

        #region Accesseurs

        /// <summary>
        /// Affecte ou retourne la propriété Input du Shader
        /// </summary>
        [System.ComponentModel.BrowsableAttribute(false)]
        public Brush Input
        {
            get
            {
                return ((Brush)this.GetValue(SwirlEffect.InputProperty));
            }
            set
            {
                this.SetValue(SwirlEffect.InputProperty, value);
            }
        }

        /// <summary>
        /// Affecte ou retourne la propriété AngleFrequency du Shader
        /// </summary>
        public Vector AngleFrequency
        {
            get
            {
                return ((Vector)this.GetValue(SwirlEffect.AngleFrequencyProperty));
            }
            set
            {
                this.SetValue(SwirlEffect.AngleFrequencyProperty, value);
            }
        }

        /// <summary>
        /// Affecte ou retourne la propriété Center du Shader
        /// </summary>
        public Point Center
        {
            get
            {
                return ((Point)this.GetValue(SwirlEffect.CenterProperty));
            }
            set
            {
                this.SetValue(SwirlEffect.CenterProperty, value);
            }
        }

        /// <summary>
        /// Retourne la propriété GeneralTransform du Shader
        /// </summary>
        protected override GeneralTransform EffectMapping
        {
            get
            {
                return (this.m_GeneralTransform);
            }
        }

        /// <summary>
        /// Affecte ou retourne la propriété SwirlStrength du Shader
        /// </summary>
        public double SwirlStrength
        {
            get
            {
                return ((double)this.GetValue(SwirlEffect.SwirlStrengthProperty));
            }
            set
            {
                this.SetValue(SwirlEffect.SwirlStrengthProperty, value);
            }
        }

        #endregion

        #region Variables statiques

        private static readonly PixelShader _PixelShader = new PixelShader();

        #endregion

        #region Variables d'instances

        private SwirlGeneralTransform m_GeneralTransform;

        #endregion

        #region Constructeurs

        /// <summary>
        /// Constructeur statique
        /// </summary>
        static SwirlEffect()
        {
            SwirlEffect._PixelShader.UriSource = new Uri("pack://application:,,,/Black.SurfaceToolkit;component/Effects/Shaders/Swirl.ps");
        }

        /// <summary>
        /// Constructeur de la classe
        /// </summary>
        public SwirlEffect()
        {
            this.PixelShader = SwirlEffect._PixelShader;

            this.UpdateShaderValue(SwirlEffect.CenterProperty);
            this.UpdateShaderValue(SwirlEffect.SwirlStrengthProperty);
            this.UpdateShaderValue(SwirlEffect.AngleFrequencyProperty);
            this.UpdateShaderValue(SwirlEffect.InputProperty);

            this.m_GeneralTransform = new SwirlGeneralTransform(this);
        }

        #endregion

        #region Classes internes

        /// <summary>
        /// Classe de transformation pour l'effet SwirlGeneralTransform
        /// </summary>
        private class SwirlGeneralTransform : GeneralTransform
        {
            #region Accesseurs

            /// <summary>
            /// Retourne la transformation
            /// </summary>
            public override GeneralTransform Inverse
            {
                get
                {
                    if (this.m_oInverseTransform == null)
                    {
                        this.m_oInverseTransform = (SwirlGeneralTransform)this.Clone();
                        this.m_oInverseTransform.m_bThisIsInverse = !this.m_bThisIsInverse;
                    }

                    return (this.m_oInverseTransform);
                }
            }

            #endregion

            #region Variables d'instance

            private bool m_bThisIsInverse;
            private readonly SwirlEffect m_oEffect;
            private SwirlGeneralTransform m_oInverseTransform;

            #endregion

            #region Constructeurs

            /// <summary>
            /// Constructer de la classe
            /// </summary>
            /// <param name="p_oEffect">Instance de l'effet</param>
            public SwirlGeneralTransform(SwirlEffect p_oEffect)
            {
                this.m_oEffect = p_oEffect;
            }

            #endregion

            #region Fonctions publiques

            /// <summary>
            /// Recalcule les bords du contrôle
            /// </summary>
            /// <param name="p_oRect">Rectangle source</param>
            /// <returns>Rectangle transformé</returns>
            public override Rect TransformBounds(Rect p_oRect)
            {
                Point oTL;
                Point oTR;
                Point oBL;
                Point oBR;

                if (this.TryTransform(new Point(p_oRect.Left, p_oRect.Top), out oTL) &&
                    this.TryTransform(new Point(p_oRect.Right, p_oRect.Top), out oTR) &&
                    this.TryTransform(new Point(p_oRect.Left, p_oRect.Bottom), out oBL) &&
                    this.TryTransform(new Point(p_oRect.Right, p_oRect.Bottom), out oBR))
                {
                    double dMaxX = Math.Max(oTL.X, Math.Max(oTR.X, Math.Max(oBL.X, oBR.X)));
                    double dMinX = Math.Min(oTL.X, Math.Min(oTR.X, Math.Min(oBL.X, oBR.X)));

                    double dMaxY = Math.Max(oTL.Y, Math.Max(oTR.Y, Math.Max(oBL.Y, oBR.Y)));
                    double dMinY = Math.Min(oTL.Y, Math.Min(oTR.Y, Math.Min(oBL.Y, oBR.Y)));

                    return (new Rect(dMinX, dMinY, dMaxX - dMinX, dMaxY - dMinY));
                }

                return (Rect.Empty);
            }

            /// <summary>
            /// Essaye de transformer un point avec l'effet Swirl
            /// </summary>
            /// <param name="p_oTargetPoint">Point a transformer</param>
            /// <param name="p_oResult">Point résultat après transformation si possible</param>
            /// <returns>TRUE transformation réussie, FALSE sinon</returns>
            public override bool TryTransform(Point p_oTargetPoint, out Point p_oResult)
            {
                Point oDir = new Point(p_oTargetPoint.X - this.m_oEffect.Center.X, p_oTargetPoint.Y - this.m_oEffect.Center.Y);
                double dL = Math.Sqrt(oDir.X * oDir.X + oDir.Y * oDir.Y);

                oDir.X = oDir.X / dL;
                oDir.Y = oDir.Y / dL;

                double dAngle = Math.Atan2(oDir.Y, oDir.X);

                double dInverseFactor = this.m_bThisIsInverse ? 1 : -1;
                double dNewAngle = dAngle + dInverseFactor * this.m_oEffect.SwirlStrength * dL;

                Point oAngleFrequency = new Point(this.m_oEffect.AngleFrequency.X, this.m_oEffect.AngleFrequency.Y);

                double dXamt = Math.Cos(oAngleFrequency.X * dNewAngle) * dL;
                double dYamt = Math.Sin(oAngleFrequency.Y * dNewAngle) * dL;

                p_oResult = new Point(this.m_oEffect.Center.X + dXamt, this.m_oEffect.Center.Y + dYamt);

                return (true);
            }

            #endregion

            #region Fonctions protégées

            /// <summary>
            /// Crée une instance
            /// </summary>
            /// <returns>Référcence de celle-ci</returns>
            protected override Freezable CreateInstanceCore()
            {
                return (new SwirlGeneralTransform(this.m_oEffect)
                {
                    m_bThisIsInverse = this.m_bThisIsInverse
                });
            }

            #endregion
        }

        #endregion
    }
}
